-- SPDX-License-Identifier: GPL-3.0-or-later
---@class RandomAI: AI
local RandomAI = AI:subclass("RandomAI")

---@param self RandomAI
---@param skill ActiveSkill
---@param card Card | nil
local function useActiveSkill(self, skill, card)
    local room = self.room
    local player = self.player

    local filter_func = skill.cardFilter
    if card then
        filter_func = function()
            return false
        end
    end

    if self.command == "PlayCard" and (not skill:canUse(player, card) or player:prohibitUse(card)) then
        return ""
    end

    local max_try_times = 100
    local selected_targets = {}
    local selected_cards = {}
    local min = skill:getMinTargetNum()
    local max = skill:getMaxTargetNum(player, card)
    local min_card = skill:getMinCardNum()
    local max_card = skill:getMaxCardNum()
    for _ = 0, max_try_times do
        if skill:feasible(selected_targets, selected_cards, self.player, card) then
            break
        end
        local avail_targets = table.filter(self.room:getAlivePlayers(), function(p)
            local ret = skill:targetFilter(p.id, selected_targets, selected_cards, card or Fk:cloneCard 'zixing')
            if ret and card then
                if player:prohibitUse(card) then
                    ret = false
                end
            end
            return ret
        end)
        avail_targets = table.map(avail_targets, function(p)
            return p.id
        end)
        local avail_cards = table.filter(player:getCardIds{Player.Hand, Player.Equip}, function(id)
            return filter_func(skill, id, selected_cards, selected_targets)
        end)

        if #avail_targets == 0 and #avail_cards == 0 then
            break
        end
        table.insertIfNeed(selected_targets, table.random(avail_targets))
        table.insertIfNeed(selected_cards, table.random(avail_cards))
    end
    if skill:feasible(selected_targets, selected_cards, self.player, card) then
        local ret = json.encode {
            card = card and card.id or json.encode {
                skill = skill.name,
                subcards = selected_cards
            },
            targets = selected_targets
        }
        -- print(ret)
        return ret
    end
    return ""
end

---@param self RandomAI
---@param skill ViewAsSkill
local function useVSSkill(self, skill, pattern, cancelable, extra_data)
    local player = self.player
    local room = self.room
    local precondition

    if self.command == "PlayCard" then
        precondition = skill:enabledAtPlay(player)
        if not precondition then
            return nil
        end
        local exp = Exppattern:Parse(skill.pattern)
        local cnames = {}
        for _, m in ipairs(exp.matchers) do
            if m.name then
                table.insertTable(cnames, m.name)
            end
        end
        for _, n in ipairs(cnames) do
            local c = Fk:cloneCard(n)
            precondition = c.skill:canUse(Self, c)
            if precondition then
                break
            end
        end
    else
        precondition = skill:enabledAtResponse(player)
        if not precondition then
            return nil
        end
        local exp = Exppattern:Parse(pattern)
        precondition = exp:matchExp(skill.pattern)
    end

    if (not precondition) or math.random() < 0.2 then
        return nil
    end

    local selected_cards = {}
    local max_try_time = 100

    for _ = 0, max_try_time do
        local avail_cards = table.filter(player:getCardIds{Player.Hand, Player.Equip}, function(id)
            return skill:cardFilter(id, selected_cards)
        end)
        if #avail_cards == 0 then
            break
        end
        table.insert(selected_cards, table.random(avail_cards))
        if skill:viewAs(selected_cards) then
            return {
                skill = skill.name,
                subcards = selected_cards
            }
        end
    end
    return nil
end

local random_cb = {}

random_cb.AskForUseActiveSkill = function(self, jsonData)
    local data = json.decode(jsonData)
    local skill = Fk.skills[data[1]]
    local cancelable = data[3]
    if cancelable and math.random() < 0.25 then
        return ""
    end
    local extra_data = json.decode(data[4])
    for k, v in pairs(extra_data) do
        skill[k] = v
    end
    return useActiveSkill(self, skill)
end

random_cb.AskForSkillInvoke = function(self, jsonData)
    return table.random {"1", ""}
end

random_cb.AskForUseCard = function(self, jsonData)
    local player = self.player
    local data = json.decode(jsonData)
    local card_name = data[1]
    local pattern = data[2] or card_name
    local cancelable = data[4] or true
    local exp = Exppattern:Parse(pattern)

    local avail_cards = table.filter(player:getCardIds("he"), function(id)
        return exp:match(Fk:getCardById(id)) and not player:prohibitUse(Fk:getCardById(id))
    end)
    if #avail_cards > 0 then
        if math.random() < 0.25 then
            return ""
        end
        avail_cards = table.map(avail_cards, function(id)
            return Fk:getCardById(id)
        end)
        for _, card in ipairs(avail_cards) do
            local skill = card.skill
            local max_try_times = 100
            local selected_targets = {}
            local min = skill:getMinTargetNum()
            local max = skill:getMaxTargetNum(player, card)
            local min_card = skill:getMinCardNum()
            local max_card = skill:getMaxCardNum()
            for _ = 0, max_try_times do
                if skill:feasible(selected_targets, {card.id}, self.player, card) then
                    break
                end
                local avail_targets = table.filter(self.room:getAlivePlayers(), function(p)
                    local ret = skill:targetFilter(p.id, selected_targets, {card}, card or Fk:cloneCard 'zixing')
                    return ret
                end)
                avail_targets = table.map(avail_targets, function(p)
                    return p.id
                end)

                if #avail_targets + #avail_cards < 1 then
                    break
                end
                table.insertIfNeed(selected_targets, table.random(avail_targets))
            end
            if skill:feasible(selected_targets, {card.id}, self.player, card) then
                return json.encode {
                    card = table.random(avail_cards),
                    targets = selected_targets
                }
            end
        end
    end
    return ""
end

---@param self RandomAI
random_cb.AskForResponseCard = function(self, jsonData)
    local data = json.decode(jsonData)
    local pattern = data[2]
    local cancelable = true
    local exp = Exppattern:Parse(pattern)
    local avail_cards = table.filter(self.player:getCardIds{Player.Hand, Player.Equip}, function(id)
        return exp:match(Fk:getCardById(id))
    end)
    if #avail_cards > 0 then
        return json.encode {
            card = table.random(avail_cards),
            targets = {}
        }
    end
    -- TODO: vs skill
    return ""
end

---@param self RandomAI
random_cb.PlayCard = function(self, jsonData)
    local cards = table.map(self.player:getCardIds(Player.Hand), function(id)
        return Fk:getCardById(id)
    end)
    local actives = table.filter(self.player:getAllSkills(), function(s)
        return s:isInstanceOf(ActiveSkill)
    end)
    local vss = table.filter(self.player:getAllSkills(), function(s)
        return s:isInstanceOf(ViewAsSkill)
    end)
    table.insertTable(cards, actives)
    table.insertTable(cards, vss)

    while #cards > 0 do
        local sth = table.random(cards)
        if sth:isInstanceOf(Card) then
            local card = sth
            local skill = card.skill ---@type ActiveSkill
            if math.random() > 0.15 then
                local ret = useActiveSkill(self, skill, card)
                if ret ~= "" then
                    return ret
                end
                table.removeOne(cards, card)
            else
                table.removeOne(cards, card)
            end
        elseif sth:isInstanceOf(ActiveSkill) then
            local active = sth
            if math.random() > 0.30 then
                local ret = useActiveSkill(self, active, nil)
                if ret ~= "" then
                    return ret
                end
            end
            table.removeOne(cards, active)
        else
            local vs = sth
            if math.random() > 0.20 then
                local ret = useVSSkill(self, vs)
                -- TODO: handle vs result
            end
            table.removeOne(cards, vs)
        end
    end

    return ""
end

function RandomAI:initialize(player)
    AI.initialize(self, player)
    self.cb_table = random_cb
end

return RandomAI
